package pa14.proj.boundschecker.main;

import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;

import soot.G;
import soot.Local;
import soot.Unit;
import soot.Value;
import soot.ValueBox;
import soot.jimple.AssignStmt;
import soot.jimple.NewArrayExpr;
import soot.jimple.internal.ImmediateBox;
import soot.jimple.internal.InvokeExprBox;
import soot.jimple.internal.JimpleLocal;
import soot.jimple.internal.JimpleLocalBox;
import soot.jimple.internal.RValueBox;
import soot.toolkits.graph.ExceptionalUnitGraph;
import soot.toolkits.scalar.BackwardFlowAnalysis;

public class BoundsCheckAnalysis extends BackwardFlowAnalysis<Unit, HashMap<Value, Pair<Value, Integer>>>{
	private boolean dl, om;
	
	private HashMap<Value, Integer> ranges = null;
	private HashMap<Value, Value> synonyms = null;
	
	public BoundsCheckAnalysis(ExceptionalUnitGraph graph, HashMap<Value, Integer> ranges, HashMap<Value, Value> synonyms, boolean debuglevel) {
		super(graph);
		this.dl = debuglevel;
		this.om = true;
		this.ranges = ranges;
		this.synonyms = synonyms;
		
		//	start analysis
		doAnalysis();
	}

	@Override
	protected void flowThrough(HashMap<Value, Pair<Value, Integer>> in, Unit thisStmt, HashMap<Value, Pair<Value, Integer>> out ) {

		if(dl)print("Statement:" + thisStmt, 1);
		if(dl)print("Useboxes:", 2);
		List<ValueBox> useBoxes = thisStmt.getUseBoxes();
		if(dl)for(ValueBox vb: useBoxes){
			print(vb, 3);
			print("isTypeImmediateBox: " + isVBInteger(vb), 4);
		}
		
		out.clear();
		out.putAll(in);
		
		if(hasAccessStatement(thisStmt)){
			if(dl)print("hasAnAccessStatement", 3);
			Value[] accessVars = getAccessStatements(thisStmt);
			if(accessVars == null)if(dl)print("no accessvars received", 3);
			Value arrayVar = accessVars[0];
			Value indexVar = accessVars[1];
			Integer rangeValue = ranges.get(arrayVar);
			if(rangeValue == null){
				print("no value found for " + arrayVar + "! Trying to find synonyms!", 5);
				Value synonymVal = synonyms.get(arrayVar);
				rangeValue = ranges.get(synonymVal);
			}
			Pair<Value, Integer> entry = new Pair<Value, Integer>(arrayVar, rangeValue);
			out.put(indexVar, entry);
			
		}
		
		if(hasConstantAssignment(thisStmt)){
			//If a constant is assigned to the variable, then check if it satisfies the range
			if(dl)print("has a constant assignment. let's check", 4);
			if(dl){
				print("printing the in set", 5);
				for(Entry<Value, Pair<Value, Integer>> thisPair: in.entrySet()){
					print(thisPair.getKey() + ": " + thisPair.getValue(), 5);
				}
			}
			Value definedValue = getDefinedVariable(thisStmt);
			Pair<Value, Integer> specifiedVar = in.get(definedValue);
			if(specifiedVar != null){
				Integer specifiedRange = specifiedVar.getSecond();
				Integer definedRange = getIntegerValueFromVB(thisStmt);
				if((definedRange.intValue() >= 0) && (definedRange.intValue() < specifiedRange.intValue())){
					if(om)print("RANGE IS OKAY", 3);
				}else{
					if(om)print("RANGE IS FAULTY", 3);
				}
			}
		}else if(hasSingleValuePassingAssignment(thisStmt)){
			//when variables are assigned to other variables
			// basically the new variable is generated and put back into the hash map
			Value definedValue = getDefinedVariable(thisStmt);
			Pair<Value, Integer> pairValue = out.get(definedValue);
			Value usedVariable = thisStmt.getUseBoxes().get(0).getValue();
			out.put(usedVariable, pairValue);
		}else if(hasValueChangingPassingAssignment(thisStmt)){
			
		}
		
	}

	private boolean hasValueChangingPassingAssignment(Unit thisStmt) {
		return ((thisStmt instanceof AssignStmt) && (thisStmt.getUseBoxes().size() == 1) && (thisStmt.getUseBoxes().get(0).getValue() instanceof Local));
	}

	private boolean isVBInteger(ValueBox vb) {
		if(!(vb.getValue() instanceof JimpleLocal) && vb.getValue().getType().toString().equals("int")){
			return true;
		}
		return false;
	}
	
	private int getIntegerValueFromVB(Unit thisStmt) {
		return Integer.parseInt(thisStmt.getUseBoxes().get(0).getValue().toString());
	}

	protected boolean hasAccessStatement(Unit thisStmt){
		List<ValueBox> accessStmts = thisStmt.getUseBoxes();
		boolean hasArrayVar = false, hasIndexVar = false, hasInvoke = false;

		for(ValueBox v: accessStmts){
			if(v instanceof JimpleLocalBox){
				hasArrayVar = true;
			}
			if(v instanceof ImmediateBox){
				hasIndexVar = true;
			}
			if(v instanceof InvokeExprBox){
				hasInvoke = true;
			}
		}
		return (hasArrayVar && hasIndexVar && !hasInvoke);
	}

	private Value[] getAccessStatements(Unit thisStmt) {
		List<ValueBox> accessStmts = thisStmt.getUseBoxes();
		
		Value arrayVar = null, indexVar = null;
		for(ValueBox v: accessStmts){
			if(v instanceof JimpleLocalBox){
				arrayVar = v.getValue();
			}
			if(v instanceof ImmediateBox){
				indexVar = v.getValue();
			}
		}
		Value[] retValue = new Value[]{arrayVar, indexVar};
		return retValue;
	}

	private boolean hasSingleStaticVariableAssignment(Unit thisStmt) {
		return ((thisStmt instanceof AssignStmt) && (thisStmt.getUseBoxes().size() == 1));
	}
	private boolean hasSingleValuePassingAssignment(Unit thisStmt){
		return ((thisStmt instanceof AssignStmt) && (thisStmt.getUseBoxes().size() == 1) && (thisStmt.getUseBoxes().get(0).getValue() instanceof Local));
	}

	private Value getDefinedVariable(Unit thisStmt) {
		return thisStmt.getDefBoxes().get(0).getValue();
	}

	private boolean hasConstantAssignment(Unit thisStmt) {
		return hasSingleStaticVariableAssignment(thisStmt) && isVBInteger(thisStmt.getUseBoxes().get(0));
	}

	@Override
	protected void copy(HashMap<Value, Pair<Value, Integer>> source, HashMap<Value, Pair<Value, Integer>> dest) {
		dest.clear();
		dest.putAll(source);
	}

	@Override
	protected void merge(HashMap<Value, Pair<Value, Integer>> in1, HashMap<Value, Pair<Value, Integer>> in2, HashMap<Value, Pair<Value, Integer>> out) {
		out.clear();
		out.putAll(in1);
		out.putAll(in2);		//	the merge operator is union
								//	because it is a may analysis
	}
	
	@Override
	protected HashMap<Value, Pair<Value, Integer>> entryInitialFlow() {
		return new HashMap<Value, Pair<Value, Integer>>();
	}

	@Override
	protected HashMap<Value, Pair<Value, Integer>> newInitialFlow() {
		return new HashMap<Value, Pair<Value, Integer>>();
	}
	
	protected boolean isCreationStatement(Unit thisStmt){
		List<ValueBox> useBoxes = thisStmt.getUseBoxes();
		
		for(ValueBox useBox: useBoxes){
			if(useBox instanceof RValueBox){
				Value boxValue = useBox.getValue();
				if(boxValue instanceof NewArrayExpr){
					return true;
				}
			}
		}
		
		return false;
	}
	
	
	protected void print(Object str, int n){
		while(n-- != 0){
			G.v().out.print("\t");
		}
		G.v().out.println(str);
	}

	public void printResults() {
		//TODO complete this function that prints out results
	}
}
